﻿namespace LogManager.Core.Models
{
    using LogManager.Core.Helpers;
    using LogManager.Utils.Unity;
    using LogManager.Utils.Xml;
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Collections.Specialized;
    using System.Linq;
    using System.Xml.Linq;
    using System.Xml.Schema;
    using LogManager.Core.Factories;

    /// <summary>
    /// TODO: Update summary.
    /// </summary>
    public class Configuration : XmlElementBase, IConfiguration
    {
        public const string DEFAULT_TEMPLATE = @"
        <?xml version='1.0' encoding='utf-8'?>
        <Configuration xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='schema.xsd'>
            " + Settings.DEFAULT_TEMPLATE + @"
            " + Warehouse.DEFAULT_TEMPLATE + @"
        </Configuration>";

        public virtual Settings Settings { get; set; }
        public virtual ObservableCollection<IWarehouse> Warehouses { get; protected set; }
        public virtual XDocument Document { get; protected set; }
        public event EventHandler<object> Changed;

        public readonly IXmlElementFactory _xmlFactory;

        public Configuration(XDocument document, IXmlElementFactory xmlFactory)
            : base(document.Root)
        {
            this._xmlFactory = xmlFactory;
            this.Document = document;
            this.Initialize();
            this.PropertyChanged += (a, b) => SaveChanges();
            this.Warehouses.CollectionChanged += (a, b) => SaveChanges();
            this.Document.Changed += Document_Changed;
        }

        private void Initialize()
        {
            var xml = new XmlResolver<Configuration>(BaseNode);
            this.Settings = xml.ResolveChild(c => c.Settings, this._xmlFactory.CreateSettings);
            this.Warehouses = new ObservableCollection<IWarehouse>(xml.ResolveChildren(c => c.Warehouses, xElm => this._xmlFactory.CreateWarehouse(xElm, this)));
        }

        public override void SaveChanges()
        {
            var xml = new XmlResolver<Configuration>(BaseNode);
            xml.SetChild(c => c.Settings, Settings);
            xml.SetChildren(c => c.Warehouses, Warehouses.OfType<IXmlElement>());
        }

        private void Document_Changed(object sender, XObjectChangeEventArgs e)
        {
            Changed(sender, e);
        }

        public IWarehouse AddNewWarehouse()
        {
            var instance = this._xmlFactory.CreateNewWarehouse(this);
            this.Warehouses.Add(instance);
            return instance;
        }

        public static XDocument LoadFile(string xmlPath, string xsdPath)
        {
            var xmlDocument = XDocument.Load(xmlPath);
            var validationErrors = new List<ValidationEventArgs>();

            var xst = new XmlSchemaSet();
            xst.Add(null, xsdPath);
            xmlDocument.Validate(xst, (o, e) => validationErrors.Add(e));

            if (validationErrors.Count > 0)
            {
                throw new XsdValidationException("Invalid configuration file.", validationErrors);
            }

            return xmlDocument;
        }

        public Configuration Clone()
        {
            return this._xmlFactory.CreateConfiguration(new XDocument(this.Document));
        }

        public void Save(string path)
        {
            this.Document.Save(path);
        }

        ISettings IConfiguration.Settings
        {
            get { return this.Settings; }
        }

        IConfiguration IConfiguration.Clone()
        {
            return this.Clone();
        }
    }
}
